Skip to content

Multigrid projection integration - #201

Merged
shaia merged 9 commits into
masterfrom
multigrid-projection-integration
Jul 24, 2026
Merged

Multigrid projection integration#201
shaia merged 9 commits into
masterfrom
multigrid-projection-integration

Conversation

@shaia

@shaia shaia commented Jul 17, 2026

Copy link
Copy Markdown
Owner

No description provided.

shaia added 2 commits July 17, 2026 17:48
One symmetric V(2,2) weighted-Jacobi multigrid cycle in Dirichlet mode per
preconditioner apply: Dirichlet matches the interior-only operator CG applies
to its zero-halo Krylov vectors (keeping M nonsingular), and equal pre/post
Jacobi sweeps keep M symmetric as CG requires. Gives grid-size-independent
convergence (5 iterations at 33..129 vs 50-170 for plain CG at tol 1e-8) on
uniform grids where Jacobi preconditioning gives no benefit.

The convenience API gains a POISSON_SOLVER_PCG_MG_SCALAR preset because
poisson_solve_3d has no params argument: the preset carries the
preconditioner into its per-preset cached instance.

Backends without the preconditioner (OMP/SIMD CG, GMRES, GPU CG) reject
POISSON_PRECOND_MULTIGRID explicitly instead of silently ignoring it.
New ns_solver_params_t.pressure_solver field selects the scalar projection's
pressure Poisson solve: default CG (0, backward compatible), standalone
multigrid V-cycles, or MG-preconditioned CG. The standalone mode subtracts
the RHS interior mean first: the projection RHS is only near-compatible and
the true Neumann system stalls on the incompatible component, while the
CG-based presets are insensitive to it.

Non-2^k+1 grids fail projection init with CFD_ERROR_UNSUPPORTED (probe-init
of the MG hierarchy), and projection_optimized/omp/gpu reject any non-default
selection at init: multigrid has no SIMD/OMP/GPU backend and cross-backend
fallbacks are forbidden.
@shaia
shaia requested a review from Copilot July 17, 2026 14:53
@shaia shaia self-assigned this Jul 17, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Integrates geometric multigrid into the pressure Poisson path for the scalar projection Navier–Stokes solver, and adds a multigrid V-cycle preconditioner option for scalar CG, with explicit “no silent fallback” behavior across other backends plus new tests and documentation.

Changes:

  • Add ns_solver_params_t.pressure_solver to select CG (default), standalone MG, or MG-preconditioned CG for the scalar projection solver.
  • Add POISSON_PRECOND_MULTIGRID and the POISSON_SOLVER_PCG_MG_SCALAR convenience preset, while explicitly rejecting MG preconditioning on non-scalar CG/GMRES backends.
  • Add convergence/selection tests and update docs/roadmap/changelog to reflect the new capabilities and constraints (2^k+1 grid dims).

Reviewed changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/solvers/navier_stokes/cpu/test_projection_pressure_solver.c Adds projection pressure-solver selection tests for MG + MG-PCG modes and backend rejection behavior.
tests/math/test_mg_pcg_convergence.c Adds MG-preconditioned CG convergence and scaling tests (2D/3D) plus backend rejection checks.
ROADMAP.md Updates roadmap items/constraints to reflect MG-PCG completion and projection wiring.
README.md Expands feature list to include GMRES and geometric multigrid (incl. preconditioner + projection pressure solve).
lib/src/solvers/navier_stokes/cpu/solver_projection.c Maps pressure_solver to Poisson presets and applies Neumann-compatibility mean subtraction for standalone MG.
lib/src/solvers/navier_stokes/cpu/solver_explicit_euler.c Initializes new pressure_solver field in ns_solver_params_default().
lib/src/solvers/navier_stokes/avx2/solver_projection_avx2.c Rejects non-default pressure_solver on SIMD projection init.
lib/src/solvers/linear/simd_template/linear_solver_gmres_simd_template.h Explicitly rejects MG preconditioner in SIMD GMRES init.
lib/src/solvers/linear/omp/linear_solver_gmres_omp.c Explicitly rejects MG preconditioner in OMP GMRES init.
lib/src/solvers/linear/omp/linear_solver_cg_omp.c Explicitly rejects MG preconditioner in OMP CG init.
lib/src/solvers/linear/neon/linear_solver_cg_neon.c Explicitly rejects MG preconditioner in NEON CG init.
lib/src/solvers/linear/linear_solver.c Adds cached POISSON_SOLVER_PCG_MG_SCALAR preset and initializes cached instance with MG preconditioner params.
lib/src/solvers/linear/linear_solver_internal.h Adds shared helper to reject MG preconditioner on unsupported backends.
lib/src/solvers/linear/gpu/poisson_solver_cg_gpu.cu Explicitly rejects MG preconditioner in GPU CG init.
lib/src/solvers/linear/cpu/linear_solver_gmres.c Explicitly rejects MG preconditioner in scalar GMRES init.
lib/src/solvers/linear/cpu/linear_solver_cg.c Implements MG V-cycle preconditioner option inside scalar CG (inner MG solver).
lib/src/solvers/linear/avx2/linear_solver_cg_avx2.c Explicitly rejects MG preconditioner in AVX2 CG init.
lib/src/api/solver_registry.c Validates MG modes at projection init time via multigrid probe-init on the exact grid.
lib/include/cfd/solvers/poisson_solver.h Adds POISSON_PRECOND_MULTIGRID and POISSON_SOLVER_PCG_MG_SCALAR to the public API.
lib/include/cfd/solvers/navier_stokes_solver.h Adds ns_pressure_solver_t and pressure_solver to the NS params public API docs/comments.
examples/poisson_solver_tuning.c Updates example grid to 65×65 and benchmarks CG+MG preconditioning; documents standalone MG Neumann compatibility caveat.
docs/reference/solvers.md Documents projection pressure_solver selection and MG preconditioner behavior/constraints.
docs/reference/api-reference.md Documents new pressure_solver field and POISSON_PRECOND_MULTIGRID/preset exposure.
docs/guides/examples.md Updates tuning-example narrative and expected output to include CG+MG PC and 2^k+1 grid note.
CMakeLists.txt Adds new test executables and registers them with CTest.
CHANGELOG.md Records the new projection wiring and MG-preconditioned CG feature additions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/src/api/solver_registry.c
Comment thread tests/solvers/navier_stokes/cpu/test_projection_pressure_solver.c
Comment thread docs/guides/examples.md

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 3 comments.

Comment thread lib/src/api/solver_registry.c Outdated
Comment thread docs/guides/examples.md
Comment thread tests/solvers/navier_stokes/cpu/test_projection_pressure_solver.c Outdated
shaia added 3 commits July 18, 2026 08:08
The MG probe-init previously mapped any failure to CFD_ERROR_UNSUPPORTED
with a grid-dimension message, masking other causes (e.g. CFD_ERROR_NOMEM)
and overwriting their error context. Only the non-2^k+1 grid rejection
(CFD_ERROR_INVALID) is remapped now; other statuses propagate unchanged so
the real failure cause survives.
The previous "zero-init backward compat" test ran make_params() twice, so
both configs went through ns_solver_params_default() and only differed by
NS_PRESSURE_SOLVER_DEFAULT vs (t)0 - the same value - making the memcmp
assertions compare identical configurations.

Build three configs from one genuinely zero-initialized struct that differ
only in pressure_solver: a zero-init run must match the explicit-CG run
bitwise and must differ from the explicit-MG run, proving the zero value
selects CG (not the multigrid path). Anchoring on a zero base avoids
default()'s nonzero source amplitudes, which otherwise confound the compare.

The helper now takes a full ns_solver_params_t so callers control exactly
how each field was initialized.
poisson_solver_tuning.c prints L2=-1.0 when a solve does not return
CFD_SUCCESS (max_iter exit), so the Jacobi row's L2=-1.0e+00 is a
"not computed" marker rather than a real negative error. Call this out
in the Expected Output so readers don't misread it as a valid value.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 2 comments.

Comment thread docs/reference/solvers.md
Comment thread lib/src/api/solver_registry.c

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 1 comment.

Comment thread lib/src/api/solver_registry.c

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 1 comment.

Comment thread lib/src/api/solver_registry.c
shaia added 4 commits July 18, 2026 20:29
The reference docs advertised a projection_jacobi_gpu solver type and an
NS_SOLVER_TYPE_PROJECTION_JACOBI_GPU macro, neither of which exists: the
registry registers NS_SOLVER_TYPE_PROJECTION_GPU ("projection_gpu") and the
GPU source is solver_projection_gpu.cu. Copy-pasting the documented name
would fail at cfd_solver_create().

Also corrects the backend table description - the GPU projection uses a
Conjugate Gradient pressure solve, not Jacobi.

Pre-existing: master already carried the stale name; this PR made it visible
by adding adjacent text that uses the correct one. Historical records under
technical-notes/ and validation/ are left as-is.
poisson_solver_create() returning NULL made projection_init return
CFD_ERROR_UNSUPPORTED with no last-error message, leaving callers and tests
without a reason for the rejection. Set an explicit message, matching the
other rejection paths in this init.
projection_init probe-initializes a multigrid solver purely to validate the
grid dimensions, but a default init builds the entire coarse-grid hierarchy
on conforming grids - an O(N) allocation and transient memory spike that the
real pressure solve then rebuilds from scratch.

Pass mg_max_levels = 1 so the probe stops at the finest level. Level 0
borrows the caller's buffers and needs no residual buffer, so the probe now
allocates only the context and one level descriptor. The 2^k+1 dimension
check runs before any of that, so the rejection behavior is unchanged.
The INVALID->UNSUPPORTED remap in projection_init assumed the probe's
CFD_ERROR_INVALID could only mean a non-2^k+1 grid. But poisson_solver_init
also returns INVALID for degenerate grids (nx<3, ny<3, or nz==2), so those
were misreported as UNSUPPORTED with a "requires 2^k+1" message.

Screen degenerate grids up front and return INVALID with a fitting message.
The two statuses drive different recovery: UNSUPPORTED tells the caller to
try another pressure solver, which is useless advice for a grid too small
for any solver. After this screen the probe's INVALID is unambiguous.

Adds test_projection_mg_rejects_degenerate_grid_as_invalid covering a 2x2
grid, alongside the existing non-2^k+1 (UNSUPPORTED) case.
@shaia
shaia merged commit f61d424 into master Jul 24, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants